#!/usr/bin/env bash

[[ -n $DEBUG ]] && set -x
set -o errtrace         # Make sure any error trap is inherited
set -o nounset          # Disallow expansion of unset variables
set -o pipefail         # Use last non-zero exit code in a pipeline

######################################################################################################
# 软件下载路径
######################################################################################################
CLASH_URL="http://all.msvc.top:30005/public/app-pkgs/raw/branch/main/Clash/clash-linux-amd64-2023.08.17.gz"
COUNTRY_URL="https://dl.ssrss.club/Country.mmdb"
CONFIG_URL=""

######################################################################################################
# 脚本设置
######################################################################################################
# 安装目录
INSTALL_DIR="/opt/clash"
# 日志文件
LOG_FILE="/tmp/clash-install-$(date +"%H%M%S").log"
# 安装步骤执行标记文件
SETUP_STATE_FILE="/tmp/clash-install-setup.txt"
# sudo用户名密码
SUDO_TAG="0"
SUDO_USER="${SUDO_USER:-root}"
SUDO_PASSWORD="${SUDO_PASSWORD:-}"
# 执行 install 指令
INSTALL_TAG="0"
# 执行 uninstall 指令
UNINSTALL_TAG="0"
# 执行 fstart 指令
FSTART_TAG="0"
# 执行 bstart 指令
BSTART_TAG="0"
# 执行 log 指令
Log_TAG="0"
# 执行 enc 指令
ENC_TAG="0"
# 当前脚本文件名称(仅显示)
SCRIPT_FILE_NAME="clash-install.sh"

######################################################################################################
# 临时变量
######################################################################################################
SECRET_STR="C7euTUNJkeoEurQfj2L5e7VdB6FocebvL5ADiNq"
ENCRYPTED_STR=""
DECRYPTED_STR=""
# 存储所有命令行参数
SCRIPT_PARAMETER="$*"
# 存储命令执行返回值的的临时变量
COMMAND_OUTPUT=""
# 普通日志信息
ACCESS_INFO="\n\033[32mACCESS Summary: \033[0m\n  "
# 错误日志信息
ERROR_INFO="\n\033[31mERROR Summary: \033[0m\n  "
# 执行过程中存在错误的标识
HAS_ERROR="0"

######################################################################################################
# 通用函数
######################################################################################################

trap trap::info 1 2 3 15 EXIT

# 信号处理
function trap::info() {
    [[ ${#ERROR_INFO} -gt 37 ]] && echo -e "$ERROR_INFO"
    [[ ${#ACCESS_INFO} -gt 38 ]] && echo -e "$ACCESS_INFO"
    [ -f "$LOG_FILE" ] && echo -e "\n\n  See detailed log >>> cat $LOG_FILE \n\n"
    trap '' EXIT
    exit
}

# 错误日志
function log::error() {
    HAS_ERROR="1"
    local item; item="[$(date +'%Y-%m-%dT%H:%M:%S.%N%z')]: \033[31mERROR:   \033[0m$*"
    ERROR_INFO="${ERROR_INFO}${item}\n  "
    echo -e "${item}" | tee -a "$LOG_FILE"
}

# 基础日志
function log::info() {
    printf "[%s]: \033[32mINFO:    \033[0m%s\n" "$(date +'%Y-%m-%dT%H:%M:%S.%N%z')" "$*" | tee -a "$LOG_FILE"
}

# 警告日志
function log::warning() {
    printf "[%s]: \033[33mWARNING: \033[0m%s\n" "$(date +'%Y-%m-%dT%H:%M:%S.%N%z')" "$*" | tee -a "$LOG_FILE"
}

# 访问信息
function log::access() {
    ACCESS_INFO="${ACCESS_INFO}$*\n  "
    printf "[%s]: \033[32mINFO:    \033[0m%s\n" "$(date +'%Y-%m-%dT%H:%M:%S.%N%z')" "$*" | tee -a "$LOG_FILE"
}

# 执行日志
function log::exec() {
    printf "[%s]: \033[34mEXEC:    \033[0m%s\n" "$(date +'%Y-%m-%dT%H:%M:%S.%N%z')" "$*" >> "$LOG_FILE"
}

# 转义引号
function utils::quote() {
    # shellcheck disable=SC2046
    if [ $(echo "$*" | tr -d "\n" | wc -c) -eq 0 ]; then
        echo "''"
    elif [ $(echo "$*" | tr -d "[a-z][A-Z][0-9]:,.=~_/\n-" | wc -c) -gt 0 ]; then
        printf "%s" "$*" | sed -e "1h;2,\$H;\$!d;g" -e "s/'/\'\"\'\"\'/g" | sed -e "1h;2,\$H;\$!d;g" -e "s/^/'/g" -e "s/$/'/g"
    else
        echo "$*"
    fi
}

# 本地执行命令
function command::exec() {
    local command="$*"
    if [[ "${SUDO_TAG:-}" == "1" ]]; then
        sudo_options="sudo -H -n -u ${SUDO_USER}"
        if [[ "${SUDO_PASSWORD:-}" != "" ]]; then
            sudo_options="${sudo_options// -n/} -p \"\" -S <<< \"${SUDO_PASSWORD}\""
        fi
        command="$sudo_options bash -c $(utils::quote "$command")"
    fi
    command="$(utils::quote "$command")"
    log::exec "[command]" "bash -c $(printf "%s" "${command//${SUDO_PASSWORD:-}/zzzzzz}")"
    # shellcheck disable=SC2094
    COMMAND_OUTPUT=$(eval bash -c "${command}" 2>> "$LOG_FILE" | tee -a "$LOG_FILE")
    local status=$?
    return $status
}

# 检查返回码
function check::exit_code() {
    local code=${1:-}
    local app=${2:-}
    local desc=${3:-}
    local exit_script=${4:-}
    if [[ "${code}" == "0" ]]; then
        log::info "[${app}]" "${desc} succeeded."
    else
        log::error "[${app}]" "${desc} failed."
        [[ "$exit_script" == "exit" ]] && exit "$code"
    fi
}

# 检查命令是否存在
function check::command_exists() {
    local cmd=${1}
    local package=${2}
    if command -V "$cmd" > /dev/null 2>&1; then
        log::info "[check]" "$cmd command exists."
    else
        log::warning "[check]" "I require $cmd but it's not installed."
        log::warning "[check]" "install $package package."
        command::exec "yum install -y ${package}"
        check::exit_code "$?" "check" "$package install" "exit"
    fi
}

# 下载文件
function utils::download_file() {
    local url="$1"
    local dest="$2"
    local unzip_tag="${3:-1}"
    local dest_dirname; dest_dirname=$(dirname "$dest")
    local filename; filename=$(basename "$dest")
    log::info "[download]" "${filename}"
    command::exec "
        set -e
        if [ ! -f \"${dest}\" ]; then
            [ ! -d \"${dest_dirname}\" ] && mkdir -pv \"${dest_dirname}\"
            wget --timeout=10 --waitretry=3 --tries=5 --retry-connrefused --no-check-certificate \"${url}\" -O \"${dest}\"
            if [[ \"${unzip_tag}\" == \"unzip\" ]]; then
                command -v unzip 2>/dev/null || yum install -y unzip
                unzip -o \"${dest}\" -d \"${dest_dirname}\"
            fi
        else
            echo \"${dest} is exists!\"
        fi
    "
    local status="$?"
    check::exit_code "$status" "download" "${filename}" "exit"
    return "$status"
}

# 检查用到的命令
function check::command() {
    check::command_exists openssl openssl
    check::command_exists wget wget
    check::command_exists gunzip gzip
}

######################################################################################################
# 安装函数
######################################################################################################

# 预检
function check::preflight() {
    # check command
    check::command
}

# 加密
function encrypt:enc() {
    local str="${1:-}"
    local secret="${2:-$SECRET_STR}"
    ENCRYPTED_STR=$(echo -n "$str" | openssl enc -aes-256-cbc -salt -pbkdf2 -iter 145 -pass pass:"$secret" | base64)
}

# 解密
function encrypt:dec() {
    local str="${1:-}"
    local secret="${2:-$SECRET_STR}"
    DECRYPTED_STR=$(echo -n "$str" | base64 --decode | openssl enc -aes-256-cbc -d -pbkdf2 -iter 145 -pass pass:"$secret")
}

######################################################################################################
# 主调用逻辑
######################################################################################################

# 安装clash
function install() {
    # 创建安装步骤执行标记文件
    [ ! -f "${SETUP_STATE_FILE}" ] && echo "" > $SETUP_STATE_FILE
    log::info "[setup]" "setup file: $SETUP_STATE_FILE"
    if grep -q "install_clash" $SETUP_STATE_FILE; then
        log::info "[skip]" "跳过已完成步骤 install"
        log::info "[install]" "启动脚本: $INSTALL_DIR/clash -d $INSTALL_DIR > $INSTALL_DIR/logs.log"
        return
    fi
    log::info "[install]" "安装clash..."
    mkdir -p $INSTALL_DIR && cd $INSTALL_DIR
    local clash_file="$INSTALL_DIR/clash-linux-amd64.gz"
    utils::download_file "$CLASH_URL" "$clash_file"
    gunzip -c *.gz > clash && chmod +x clash
    utils::download_file "$COUNTRY_URL" "$INSTALL_DIR/Country.mmdb"
    encrypt:dec "$CONFIG_URL"
    local url=$DECRYPTED_STR
    wget -q --timeout=10 --waitretry=3 --tries=5 --retry-connrefused --no-check-certificate "${url}" -O "$INSTALL_DIR/config.yaml"
    cat << EOF >> "$INSTALL_DIR/config.yaml"
dns:
  enable: true
  listen: 0.0.0.0:53
  enhanced-mode: fake-ip
  nameserver:
    - 114.114.114.114
  fallback:
    - 8.8.8.8
tun:
  enable: true
  stack: system
  dns-hijack:
    - 8.8.8.8:53
    - tcp://8.8.8.8:53
    - any:53
    - tcp://any:53
  auto-route: true
  auto-detect-interface: true
EOF
    echo "install_clash" >> $SETUP_STATE_FILE
    log::info "[install]" "安装clash成功"
    log::info "[install]" "启动脚本: $INSTALL_DIR/clash -d $INSTALL_DIR > $INSTALL_DIR/logs.log"
}

# 卸载clash
function uninstall() {
    log::info "[uninstall]" "卸载clash..."
    rm -rf $INSTALL_DIR
    rm -f $SETUP_STATE_FILE
    log::info "[uninstall]" "卸载clash成功"
}

# 前台启动clash
function fstart() {
    log::info "[fstart]" "前台启动clash..."
    $INSTALL_DIR/clash -d $INSTALL_DIR
}

# 后台启动clash
function bstart() {
    log::info "[bstart]" "后台启动clash..."
    nohup $INSTALL_DIR/clash -d $INSTALL_DIR > $INSTALL_DIR/logs.log 2>&1 &
    log::info "[bstart]" "后台启动clash成功"
    log::info "[bstart]" "tail -F $INSTALL_DIR/logs.log"
}

# 查看clash日志
function log() {
    log::info "[log]" "查看clash日志..."
    tail -F $INSTALL_DIR/logs.log
}

# 加密字符串
function enc() {
    log::info "[enc]" "加密字符串..."
    encrypt:enc "$CONFIG_URL"
    log::info "[enc]" "[$ENCRYPTED_STR]"
}

######################################################################################################
# main-入口
######################################################################################################

# 使用帮助
function help::usage() {
    cat << EOF
安装clash透明代理。

Usage:
  $(basename "$SCRIPT_FILE_NAME") [command]

Available Commands:
  install       安装clash
  uninstall     卸载clash
  fstart        前台启动clash
  bstart        后台启动clash
  log           查看clash日志

Flag:
  -c,--config          配置文件
      --sudo           sudo模式
      --sudo-user      sudo用户
      --sudo-password  sudo用户密码

Example:
  [安装clash]
  $SCRIPT_FILE_NAME install \\
  --c "......"

  [前台启动clash]
  $SCRIPT_FILE_NAME fstart
EOF
    exit 1
}

# 打印日志文件
echo -e "\033[36m tail -F $LOG_FILE -n 100 \033[0m"
# 解析命令行参数
[ "$#" == "0" ] && help::usage
while [ "${1:-}" != "" ]; do
    case $1 in
        install  )              INSTALL_TAG=1
                                ;;
        uninstall  )            UNINSTALL_TAG=1
                                ;;
        fstart  )               FSTART_TAG=1
                                ;;
        bstart  )               BSTART_TAG=1
                                ;;
        log  )                  Log_TAG=1
                                ;;
        enc  )                  ENC_TAG=1
                                ;;
        -c | --config )         shift
                                CONFIG_URL=${1:-$CONFIG_URL}
                                ;;
        -s | --secret )         shift
                                SECRET_STR=${1:-$SECRET_STR}
                                ;;
        --sudo )                SUDO_TAG=1
                                ;;
        --sudo-user )           shift
                                SUDO_USER=${1:-$SUDO_USER}
                                ;;
        --sudo-password )       shift
                                SUDO_PASSWORD=${1:-}
                                ;;
        * )                     help::usage
                                exit 1
    esac
    shift
done

# 开始
log::info "[start]" "bash $SCRIPT_FILE_NAME ${SCRIPT_PARAMETER//${SUDO_PASSWORD:-}/zzzzzz}"
# 预检
check::preflight
# 动作
if [[ "${INSTALL_TAG:-}" == "1" ]]; then
    install
elif [[ "${UNINSTALL_TAG:-}" == "1" ]]; then
    uninstall
elif [[ "${FSTART_TAG:-}" == "1" ]]; then
    fstart
elif [[ "${BSTART_TAG:-}" == "1" ]]; then
    bstart
elif [[ "${Log_TAG:-}" == "1" ]]; then
    log
elif [[ "${ENC_TAG:-}" == "1" ]]; then
    enc
else
    help::usage
fi

# bash <(curl -s http://all.msvc.top:30005/public/helm-chart/raw/branch/master/01shell/clash-install-rhel.sh) [cmd]
